package com.cskaoyan.javase.object._4hashcode;

/**
 * hashCode方法
 * 1.方法的声明
 * hashCode()方法的方法声明：
 * public native int hashCode();
 * 很显然，它是一个本地方法，这个方法也没有任何参数，返回值是一个int类型整数。
 *
 * 2.方法的作用
 * 由于它是一个本地方法，我们通过查阅JDK文档，文档中关于它的作用说明了两点：
 *      a.返回该对象的哈希码值。
 *          Java代码中的哈希映射就是将某个类的对象(集合A),全部映射到int整数上(集合B)
 *          hashCode方法的实现就是一种映射的对应规则
 *          其中集合B中的元素也就是 hashCode方法的返回值,int整数,就是该对象的哈希值.
 *
 *          设计String类的哈希算法
 *          这个字符串长度是几,哈希值就是几
 *          "abc" --> 3
 *          "1234" --> 4
 *          但是这个算法的哈希冲突太多了,不是很好的算法
 *
 *
 *      b.支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 *
 *      Java当中的哈希表是这么进行存储的
 *      对于一个键值对的对象,key-value
 *      首先通过计算key对象的哈希值,然后通过哈希值决定这个键值对在哈希表中的存储位置(数组的下标)
 *      而一旦出现哈希冲突,需要将两个键值对存储在同一个存储单元,就会采用拉链法
 *      就是将一个键值对对象的引用作为成员变量持有,从而形成一条链表
 *
 *      在哈希表中,显然哈希冲突是越少越好的,这就依赖于良好的哈希算法的实现
 *      就是hashCode方法的实现.
 *
 * 关于哈希表、哈希码值等相关的概念，如果你对数据结构这门课程有些了解，那么这些概念就是非常容易的。当然如果你还不太明白，就看一下下面的说明。
 *
 * 那么equals方法为什么hashCode方法为什么一定要一起重写呢?
 * 键值对中key是唯一性标识,哈希表中不存在两个相同的键
 * 那么怎么判断两个key是相等/相同的呢?
 * 通过key对象类型当中的equals方法决定
 * equals方法返回true,就是相同的key,不会存储/或者覆盖
 * 而如果equals方法返回false,那么就是不相同的key,就会存储进哈希表
 *
 * 那么这样:
 * 1.如果equals方法对两个key返回true,那么它们是同一个(相同的)对象
 * 就意味着它们应该是哈希映射当中集合A中的同一个元素,那么它们哈希映射的结果必须一样,哈希值必须一样,这样它们的hashCode方法的返回值也必须一样.(这是必须满足的,不满足就不是映射了)
 * 为了满足以上特点,那么equals方法和hashCode方法要么一起重写,要么一起不被重写.
 *
 * 2.如果equals方法对两个key返回false,那么它们不是同一个(相同的)对象
 * 就意味着它们不是哈希映射当中集合A中的同一个元素,那么这时它们的哈希值没有要求,可以不同,也可以相同(相同称之为哈希冲突)
 * 虽然不能强制要求,一定不同,但是我们可以追求减少哈希冲突,尽量让它们不同.
 *
 *
 * hashCode方法如果不重写,那么它的默认实现是什么呢?
 * JDK文档中的原话:
 * "实际上，由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。（这一般是通过将该对象的内部地址转换成一个整数来实现的，但是 JavaTM 编程语言不需要这种实现技巧。）"
 * 说明 默认实现是通过对象地址计算哈希值的
 * 这就和不重写的equals方法保持了一致性: equals方法返回true,哈希值必须相同!
 *
 *
 * 如果重写equals方法,那就就要同时同时hashCode方法
 * 在很多时候，我们都需要重写hashCode方法，JDK文档中给我们标注了重写的规则和什么时候进行重写，如下：
 *      1.在 Java 应用程序执行期间，在对同一对象多次调用 hashCode 方法时，必须一致地返回相同的整数，前提是将对象进行 equals比较时所用的信息没有被修改。
 *      equals方法的重写是前提,hashCode方法的重写要看equals方法
 *
 *      2.如果根据 equals(Object)方法，两个对象是相等的，那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。
 *      因为映射不允许"一对多",所以这里它们的哈希值必须相同
 *      怎么保证相同呢?
 *      只要按照重写equals方法规则,重写hashCode方法就行
 *      比如equals方法按照age,score的取值重写,那hashCode方法也必须按照它们的取值重写.
 *
 *      3.如果根据 equals(java.lang.Object)方法，两个对象不相等，那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果。但是，程序员应该意识到，为不相等的对象生成不同整数结果可以提高哈希表的性能。
 *      哈希冲突一定存在,但是减少哈希冲突是终极追求.
 *      哈希冲突是允许存在（不允许也不可能）的，但是我们要清楚，设计出更好的哈希算法，尽量避免哈希冲突，可以提升哈希表性能。（ 虽然完美永远是达不到的，但我们要相信光。）
 *
 *
 * 最后，还是要强调一下，hashCode方法和equals方法要重写必须一起重写，而且重写的依据必须是一样的，这样才能保证两个方法的结果具有一致性。
 * 比如一个Student类，如果equals方法依据其中的成员变量age和name重写的，那么hashCode方法也必须要依赖于这两个成员变量重写！！
 *
 * 注意事项
 * 1.一旦重写hashCode方法后，默认的toString方法就不会再打印地址值了。这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值，重写后不再是地址值了。
 * 一旦重写hashCode方法,要么不要使用toString方,要么就按照成员变量的取值重写它
 *
 * 2.如果类中有引用数据类型成员变量，那么就继续调用它的hashCode方法，也就需要重写它的hashCode方法。
 *
 *
 *
 * @since 14:49
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        Student s3 = s1;
        System.out.println(s1.equals(s3));
        System.out.println(s1.hashCode());
        System.out.println(s3.hashCode());

        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        Student s4 = new Student(17, 101);
        System.out.println(s1.equals(s4));
        System.out.println(s1.hashCode());
        System.out.println(s4.hashCode());

        System.out.println("--------------");
        // 对于String对象而言,只要是字符串内容相同,那么哈希值一定相同
        String str1 = "hello";
        String str2 = new String("hello");
        System.out.println(str1 == str2);
        System.out.println(str1.equals(str2));
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());

        System.out.println("------------------------------");
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
    }
}

class Student {
    int age;
    double score;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }
}

class Teacher {
    private int age;
    private String name;
    private int id;
    private String gender;
    private double salary;
    private double bonus;

    public Teacher() {
    }

    public Teacher(int age, String name, int id, String gender, double salary, double bonus) {
        this.age = age;
        this.name = name;
        this.id = id;
        this.gender = gender;
        this.salary = salary;
        this.bonus = bonus;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Teacher teacher = (Teacher) o;

        if (getAge() != teacher.getAge()) return false;
        if (getId() != teacher.getId()) return false;
        if (Double.compare(teacher.getSalary(), getSalary()) != 0) return false;
        if (Double.compare(teacher.getBonus(), getBonus()) != 0) return false;
        if (getName() != null ? !getName().equals(teacher.getName()) : teacher.getName() != null) return false;
        return getGender() != null ? getGender().equals(teacher.getGender()) : teacher.getGender() == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = getAge();
        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
        result = 31 * result + getId();
        result = 31 * result + (getGender() != null ? getGender().hashCode() : 0);
        temp = Double.doubleToLongBits(getSalary());
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        temp = Double.doubleToLongBits(getBonus());
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", id=" + id +
                ", gender='" + gender + '\'' +
                ", salary=" + salary +
                ", bonus=" + bonus +
                '}';
    }
}